﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Daidalos.Exception;

namespace Daidalos.Font
{

    public sealed class BitmapString : Interfaces.Cloneable<BitmapString>
    {

        #region Attributes
        /// <summary>
        ///     Specifies the x-position of the string.
        /// </summary>
        private float x = 0.0f;

        /// <summary>
        ///     Specifies the y-position of the string.
        /// </summary>
        private float y = 0.0f;

        /// <summary>
        ///     The overall font scalation factor.
        /// </summary>
        private float scaleFactor = 1.0f;

        /// <summary>
        ///     The text contained in the bitmap string.
        /// </summary>
        private string text = null;

        /// <summary>
        ///     Store the capacity of the buffer in its
        ///     current form.
        /// </summary>
        private int bufferCapacity = 0;

        /// <summary>
        ///     The character set object encoding the font
        ///     used by this string.
        /// </summary>
        private CharacterSet font = null;

        /// <summary>
        ///     The vertex buffer interfaces the engine with
        ///     the vertices stored in the graphics device.
        /// </summary>
        private Microsoft.DirectX.Direct3D.VertexBuffer vertexBuffer = null;

        /// <summary>
        ///     The index buffer interfaces the engine with
        ///     the index list stored on the graphcis device.
        /// </summary>
        private Microsoft.DirectX.Direct3D.IndexBuffer indexBuffer = null;

        /// <summary>
        /// 
        /// </summary>
        private System.Drawing.Color textColor = System.Drawing.Color.White;

        /// <summary>
        /// 
        /// </summary>
        private System.Drawing.Point position = new System.Drawing.Point(0, 0);

        /// <summary>
        ///     Stores the range of drawn indices.
        /// </summary>
        private int indexRange = 0;
        #endregion

        #region Properties
        /// <summary>
        ///     Implements accessors to the x-position.
        /// </summary>
        public float X
        {
            get
            {
                return x;
            }
            set
            {
                x = value;
            }
        }

        /// <summary>
        ///     Implements accessors to the y-position.
        /// </summary>
        public float Y
        {
            get
            {
                return y;
            }
            set
            {
                y = value;
            }
        }

        /// <summary>
        ///     Implements accessors to the position.
        /// </summary>
        public System.Drawing.PointF Position
        {
            get
            {
                return new System.Drawing.PointF(x, y);
            }
            set
            {
                x = value.X;
                y = value.Y;
            }
        }
    
        /// <summary>
        ///     Implements accessors to the text of the string.
        /// </summary>
        public string Text
        {
            get
            {
                return text;
            }
            set
            {
                text = value;
                BuildBuffer();
            }
        }

        /// <summary>
        ///     Implements accessors to the text color.
        /// </summary>
        public System.Drawing.Color TextColor
        {
            get
            {
                return textColor;
            }
            set 
            { 
                textColor = value;
                BuildBuffer();
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        ///     Creates a bitmap string containing the supplied
        ///     text displayed using the character set labeled
        ///     with the supplied fontname.
        /// </summary>
        /// <param name="text">
        ///     The desired text.
        /// </param>
        /// <param name="font">
        ///     The name of the desired character set.
        /// </param>
        public BitmapString(string text, string font, float scaleFactor, System.Drawing.Color textColor)
        {
            this.textColor = textColor;
            this.scaleFactor = (scaleFactor > 0.0f) ? scaleFactor : 1.0f;
            this.text = text;
			this.font = Daidalos.Font.CharacterSet.GetSetByName(font);
            BuildBuffer();
		}

        /// <summary>
        ///     Releases the AGP memory used by the string.
        /// </summary>
        ~BitmapString()
        {
            if (vertexBuffer != null)
            {
                vertexBuffer.Dispose();
                vertexBuffer = null;
            }
            if (indexBuffer != null)
            {
                indexBuffer.Dispose();
                indexBuffer = null;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        ///     Create a bitmap string, similar to this one.
        /// </summary>
        /// <returns>
        ///     A similar bitmap string object.
        /// </returns>
        public BitmapString Clone()
        {
            BitmapString clone = new BitmapString(text, font.Name, scaleFactor, textColor);
            return clone;
        }

		/// <summary>
		///		Prepare the DirectX device to render
		///		the bitmap string.
		/// </summary>
        public void PrepareOnce()
        {
            // Lazy bind the font texture to the first texture unit.
            font.Texture.Bind(0);

            // Fetch the rendering device.
            Microsoft.DirectX.Direct3D.Device device = Graphics.RenderDevice.Instance.Device;

            // Setup the renderstates.
            device.RenderState.ZBufferEnable = false;
            device.RenderState.ZBufferWriteEnable = false;
            device.RenderState.FogEnable = false;
            device.RenderState.AlphaTestEnable = false;
            device.RenderState.AlphaBlendEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.InvSourceAlpha;

            // Setup the texturestages.
            device.SetTextureStageState(0, TextureStageStates.AlphaOperation,
                (int)TextureOperation.Modulate);

            // Setup the samplerstates.
            device.SamplerState[0].AddressU = TextureAddress.Clamp;
            device.SamplerState[0].AddressV = TextureAddress.Clamp;
            device.SamplerState[0].MinFilter = TextureFilter.Linear;
            device.SamplerState[0].MagFilter = TextureFilter.Linear;

            // Setup the vertex format used by the bitmap fonts.
            device.VertexFormat = CustomVertex.PositionColoredTextured.Format;
        }

        /// <summary>
        ///     Displayes the bitmap at the projection's origin.
        /// </summary>
        public void Display()
        {
            // Anything to do?
            if (indexBuffer == null || vertexBuffer == null)
            {
                return;
            }

            // Bind the buffers.
            Graphics.RenderDevice.Instance.Device.SetStreamSource(0, vertexBuffer, 0);
            Graphics.RenderDevice.Instance.Device.Indices = indexBuffer;

            // Draw the string.
            Graphics.RenderDevice.Instance.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, indexRange, 0, indexRange / 3);
        }

        /// <summary>
        ///     This method uses the text and font to create
        ///     a vertex and an index buffer that are required
        ///     to render the text to the screen.
        /// </summary>
        private void BuildBuffer()
        {
            // Determine the text length.
            int textLength = text.Length;

            // Have the buffers been used before?
            if (vertexBuffer != null)
            {
                // Is the previously allocated buffer to small to
                // store the new text string?
                if (textLength > bufferCapacity)
                {
                    vertexBuffer.Dispose();
                    vertexBuffer = null;
                }
            }
            if (indexBuffer != null)
            {
                // Is the previously allocated buffer to small to
                // store the new text string?
                if (textLength > bufferCapacity)
                {
                    indexBuffer.Dispose();
                    indexBuffer = null;
                }
            }
            
            // Is there something to do?
            if (text == null)
            {
                return;
            }

            // We need a character set to do something.
            if (font == null)
            {
                throw new GenericException("Assign a character set before building a bitmap-string's buffer!");
            }

            // Determine the required amount of vertices.
            int numVertices = 4 * textLength;

            // Try to allocate a software buffer for the vertices.
            CustomVertex.PositionColoredTextured[] vertices = new CustomVertex.PositionColoredTextured[numVertices];
            if (vertices == null)
            {
                throw new GenericException("Failed to allocate the system vertex buffer!");
            }

            // Instanciate the vertices.
            float xOffset = X;
            float yOffset = Y;
            int vOffset = 0;
            int numIndices = 0;
            for (int i = 0; i < textLength; ++i)
            {
				if (text[i] == ' ')
				{
					xOffset += font.WhiteSpaceLeap;
					continue;
				}
                if(text[i] == '\n')
                {
                    yOffset += 10;
                    xOffset = position.X;
                }

                // Retrieve the character.
                int code = (int)text[i];
                if (font.Characters.Length < code)
                {
                    xOffset += font.WhiteSpaceLeap;
                    continue;
                }
                Daidalos.Font.Character symbol = font.Characters[code];
                if (symbol == null)
                {
                    xOffset += font.WhiteSpaceLeap;
                    continue;
                }
                float yMargin = y + symbol.YOffset * scaleFactor;

                // Build the top left vertex.
                vertices[vOffset].X = xOffset + position.X;
                vertices[vOffset].Y = yMargin + position.Y + yOffset;
				vertices[vOffset].Z = 1.0f;
                vertices[vOffset].Tu = (float)(symbol.X) / (float)font.TextureWidth;
                vertices[vOffset].Tv = (float)(symbol.Y) / (float)font.TextureHeight;
                vertices[vOffset].Color = TextColor.ToArgb();

                // Build the top right vertex.
                vertices[vOffset + 1].X = xOffset + (symbol.Width + font.SpacingX) * scaleFactor + position.X;
                vertices[vOffset + 1].Y = yMargin + position.Y + yOffset;
				vertices[vOffset + 1].Z = 1.0f;
				vertices[vOffset + 1].Tu = (float)(symbol.X + symbol.Width + font.SpacingX) / (float)font.TextureWidth;
                vertices[vOffset + 1].Tv = (float)(symbol.Y) / (float)font.TextureHeight;
                vertices[vOffset + 1].Color = TextColor.ToArgb();

                // Build the bottom right vertex.
                vertices[vOffset + 2].X = xOffset + (symbol.Width + font.SpacingX) * scaleFactor + position.X;
                vertices[vOffset + 2].Y = yMargin + symbol.Height * scaleFactor + position.Y + yOffset;
				vertices[vOffset + 2].Z = 1.0f;
                vertices[vOffset + 2].Tu = (float)(symbol.X + symbol.Width + font.SpacingX) / (float)font.TextureWidth;
                vertices[vOffset + 2].Tv = (float)(symbol.Y + symbol.Height + font.SpacingY + 1) / (float)font.TextureHeight;
                vertices[vOffset + 2].Color = TextColor.ToArgb();

                // Build the bottom left vertex.
                vertices[vOffset + 3].X = xOffset + position.X;
                vertices[vOffset + 3].Y = yMargin + symbol.Height * scaleFactor + position.Y + yOffset;
				vertices[vOffset + 3].Z = 1.0f;
                vertices[vOffset + 3].Tu = (float)(symbol.X) / (float)font.TextureWidth;
                vertices[vOffset + 3].Tv = (float)(symbol.Y + symbol.Height + font.SpacingY + 1) / (float)font.TextureHeight;
                vertices[vOffset + 3].Color = TextColor.ToArgb();

                // Shift the offsets.
                xOffset += symbol.Leap * scaleFactor;
                vOffset += 4;

                // Increase number of required indices.
                numIndices += 6;
            }

            // Allocate the system index buffer.
            short[] indices = new short[numIndices];
            if( indices == null )
            {
                throw new GenericException("Failed to allocate the system index buffer!");
            }

            // Store the required range of indices.
            indexRange = numIndices;
            
            // Build the indices.
            int iOffset = 0;
            vOffset = 0;
            while (iOffset < numIndices)
            {
                // Build a set of indices.
                indices[iOffset] = (short)(vOffset + 3);
                indices[iOffset + 1] = (short)vOffset;
                indices[iOffset + 2] = (short)(vOffset + 2);
                indices[iOffset + 3] = (short)vOffset;
                indices[iOffset + 4] = (short)(vOffset + 1);
                indices[iOffset + 5] = (short)(vOffset + 2);
                
                // Shift offsets.
                iOffset += 6;
                vOffset += 4;
            }

            // Is a reallocation required?
            if (vertexBuffer == null)
            {
                // Allocate the buffer.
                vertexBuffer = new Microsoft.DirectX.Direct3D.VertexBuffer(typeof(CustomVertex.PositionColoredTextured),
                    numVertices, Graphics.RenderDevice.Instance.Device, Usage.WriteOnly, CustomVertex.PositionColoredTextured.Format,
                    Pool.Default);

                // Store the buffer's capacity.
                bufferCapacity = textLength;
            }
            if (vertexBuffer == null)
            {
                throw new GenericException("Failed to instanciate a vertex buffer!");
            }

            // Transfer the vertices to the graphics accelerator.
            Microsoft.DirectX.GraphicsStream stream = vertexBuffer.Lock(0, 0, 0);
            stream.Write(vertices);
            vertexBuffer.Unlock();

            // Is a reallocation required?
            if (indexBuffer == null)
            {
                // Allocate the buffer.
                indexBuffer = new Microsoft.DirectX.Direct3D.IndexBuffer(typeof(short), numIndices, Graphics.RenderDevice.Instance.Device,
                    Usage.WriteOnly, Pool.Default);

                // Store the buffer's capacity.
                bufferCapacity = textLength;
            }
            if (indexBuffer == null)
            {
                throw new GenericException("Failed to instanciate an index buffer!");
            }

            // Transfer the indices to the graphcis accelerator.
            stream = indexBuffer.Lock(0, 0, 0);
            stream.Write(indices);
            indexBuffer.Unlock();
        }
        #endregion

    }

}
